package com.gitee.wsl.struct.either

import arrow.core.Either
import com.gitee.wsl.base.None
import com.gitee.wsl.base.Option
import com.gitee.wsl.base.Some
import com.gitee.wsl.struct.either.ext.asOption

data class LeftProjection<out L, out R>(val either: Either<L, R>) {

    /**
     * Gets value of this [Left].
     *
     * @return Value of this [Left].
     *
     * @throws NoSuchElementException If this is [Right].
     */
    fun get(): L = when (either) {
        is Either.Left -> either.value
        is Either.Right -> throw NoSuchElementException("Getting Left value from Right")
    }

    /**
     * Gets value of this [Left] or `null` if this is [Right].
     *
     * @return Value of this [Left] or `null`.
     */
    fun getOrNull(): L? = when (either) {
        is Either.Left -> either.value
        is Either.Right -> null
    }

    /**
     * Runs [action] if this is a [Left]. Returns [Unit] without any action if this is a [Right].
     *
     * @param action Action to be run on a [Left].
     */
    inline fun forEach(action: (L) -> Unit) = when (either) {
        is Either.Left -> action(either.value)
        is Either.Right -> Unit
    }

    /**
     * Maps value of this [Left] using [transform].
     *
     * @param transform Function transforming a [Left].
     *
     * @return [Left] mapped using [transform] or this object if this is a [Right].
     */
    inline fun <T> map(transform: (L) -> T): Either<T, R> = when (either) {
        is Either.Left -> Either.Left(transform(either.value))
        is Either.Right -> either
    }

    /**
     * Returns the result of applying the [predicate] to the value if this is [Left]
     * or `true` if this is [Right].
     *
     * @param predicate Predicate function.
     *
     * @return The result of applying the [predicate] to the value if this is [Left]
     * or `true` if this is [Right].
     */
    inline fun all(predicate: (L) -> Boolean): Boolean = when (either) {
        is Either.Left -> predicate(either.value)
        is Either.Right -> true
    }

    /**
     * Returns the result of applying the [predicate] to the value if this is [Left]
     * or `false` if this is [Right].
     *
     * @param predicate Predicate function.
     *
     * @return The result of applying the [predicate] to the value if this is [Left]
     * or `false` if this is [Right].
     */
    inline fun any(predicate: (L) -> Boolean): Boolean = when (either) {
        is Either.Left -> predicate(either.value)
        is Either.Right -> false
    }

    /**
     * Returns `false` if the [predicate] is met by the value if this is [Left] or `true` otherwise.
     *
     * @param predicate Predicate function.
     *
     * @return `false` if the [predicate] is met by the value if this is [Left] or `true` otherwise.
     *
     * @since 1.1
     */
    inline fun none(predicate: (L) -> Boolean): Boolean = when (either) {
        is Either.Left -> !predicate(either.value)
        is Either.Right -> true
    }

    /**
     * Returns the same [Left] if the [predicate] is satisfied for the value. Otherwise returns `null`.
     *
     * @param predicate Predicate function.
     *
     * @return The same [Left] if the [predicate] is satisfied for the value. Otherwise returns `null`.
     */
    inline fun filter(predicate: (L) -> Boolean): Either<L, R>? = when (either) {
        is Either.Left -> either.takeIf { predicate(it.value) }
        is Either.Right -> null
    }

    /**
     * Returns the same [Left] if the [predicate] is not satisfied for the value. Otherwise returns `null`.
     *
     * @param predicate Predicate function.
     *
     * @return The same [Left] if the [predicate] is not satisfied for the value. Otherwise returns `null`.
     */
    inline fun filterNot(predicate: (L) -> Boolean): Either<L, R>? = when (either) {
        is Either.Left -> either.takeUnless { predicate(it.value) }
        is Either.Right -> null
    }

    /**
     * Returns the same [Left] casted to type [T] if it is [T]. Otherwise returns `null`.
     *
     * @param T Required type of the optional value.
     *
     * @return The same [Left] casted to type [T] if it is [T]. Otherwise returns `null`.
     */
    inline fun <reified T> filterIsInstance(): Either<T, R>? = when (either) {
        is Either.Left -> (either.value as? T)?.let { Either.Left(it) }
        is Either.Right -> null
    }

    /**
     * Returns [Some] containing the same [Left] if the [predicate] is satisfied for the value.
     * Otherwise returns [None].
     *
     * @param predicate Predicate function.
     *
     * @return [Some] containing the same [Left] if the [predicate] is satisfied for the value.
     * Otherwise returns [None].
     */
    inline fun filterToOption(predicate: (L) -> Boolean): Option<Either<L, R>> = when (either) {
        is Either.Left -> if (predicate(either.value)) Some(either) else None
        is Either.Right -> None
    }

    /**
     * Returns [Some] containing the same [Left] if the [predicate] is not satisfied for the value.
     * Otherwise returns [None].
     *
     * @param predicate Predicate function.
     *
     * @return [Some] containing the same [Left] if the [predicate] is not satisfied for the value.
     * Otherwise returns [None].
     */
    inline fun filterNotToOption(predicate: (L) -> Boolean): Option<Either<L, R>> = when (either) {
        is Either.Left -> if (predicate(either.value)) None else Some(either)
        is Either.Right -> None
    }

    /**
     * Returns [Some] containing the same [Left] casted to type [T] if it is [T]. Otherwise returns [None].
     *
     * @param T Required type of the optional value.
     *
     * @return [Some] containing the same [Left] casted to type [T] if it is [T]. Otherwise returns [None].
     */
    inline fun <reified T> filterIsInstanceToOption(): Option<Either<T, R>> = when (either) {
        is Either.Left -> (either.value as T?)?.let { Either.Left(it) }.asOption()
        is Either.Right -> None
    }

    /**
     * Returns a [Some] containing the [Left] value if it exists, or a [None] if this is a [Right].
     *
     * @return a [Some] containing the [Left] value if it exists, or a [None] if this is a [Right].
     */
    fun toOption(): Option<L> = when (either) {
        is Either.Left -> Option(either.value)
        is Either.Right -> None
    }
}

/**
 * Gets value of this [Left] or [default] value if this is [Right].
 *
 * @param default Default value provider.
 *
 * @return Value of this [Left] or [default].
 */
inline fun <L, R> LeftProjection<L, R>.getOrElse(default: () -> L): L = when (either) {
    is Either.Left -> either.value
    is Either.Right -> default()
}

/**
 * Maps value of this [Left] to a new [Either] using [transform].
 *
 * @param transform Function transforming a [Left] to an [Either].
 *
 * @return [Either] mapped using [transform] or this object if this is a [Right].
 */
inline fun <L, R, T> LeftProjection<L, R>.flatMap(transform: (L) -> Either<T, R>): Either<T, R> = when (either) {
    is Either.Left -> transform(either.value)
    is Either.Right -> either
}

/**
 * Returns the same [Left] if its value is not `null`. Otherwise returns `null`.
 *
 * @return The same [Left] if its value is not `null`. Otherwise returns `null`.
 */
fun <L, R> LeftProjection<L?, R>.filterNotNull(): Either<L, R>? = when (either) {
    is Either.Left -> either.value?.let { Either.Left(it) }
    is Either.Right -> null
}

/**
 * Returns [Some] containing the same [Left] if its value is not `null`. Otherwise returns [None].
 *
 * @return [Some] containing the same [Left] if its value is not `null`. Otherwise returns [None].
 */
fun <L, R> LeftProjection<L?, R>.filterNotNullToOption(): Option<Either<L, R>> = when (either) {
    is Either.Left -> either.value?.let { Either.Left(it) }.asOption()
    is Either.Right -> None
}

/**
 * Returns the same [Left] if the [predicate] is satisfied for the value,
 * `Right(zero)` if the [predicate] is not satisfied for the value,
 * or the same [Right] if this is [Right].
 *
 * @param predicate Predicate function.
 * @param zero Provider of the value used if the [predicate] is not satisfied.
 *
 * @return The same [Left] if the [predicate] is satisfied for the value,
 * `Right(zero)` if the [predicate] is not satisfied for the value,
 * or the same [Right] if this is [Right].
 *
 * @since 1.2
 */
inline fun <L, R> LeftProjection<L, R>.filterOrElse(predicate: (L) -> Boolean, zero: () -> R): Either<L, R> =
    when (either) {
        is Either.Left -> if (predicate(either.value)) either else Either.Right(zero())
        is Either.Right -> either
    }